<template>
  <div class="view-container q-my-md">
    <q-linear-progress v-if="max > 0" size="20px" :value="step/max" color="blue-8" class="q-mb-md">
      <div class="absolute-full flex flex-center">
        <q-badge color="white" text-color="accent" :label="processLabel" />
      </div>
    </q-linear-progress>

    <viewer :images="images" @inited="inited" class="viewer" ref="viewer">
      <template #default="scope">
        <div class="row q-gutter-lg items-center justify-center">
          <div v-for="(img, idx) in scope.images" :key="img.thumb">
            <div class="row image-box">
              <img class="fit-thumb" :src="imageUrl(img)" :data-src="img.image" />
            </div>

            <div class="q-mb-sm text-center btn-fixed-width" v-if="!readonly && img.canCompress">
              <q-checkbox
                dense
                size="sm"
                left-label
                v-model="img.compress"
                label="压缩图片"
              />
            </div>

            <div class="cursor-pointer q-mb-sm btn-fixed-width">
              {{ !readonly ? img.title || '添加标题' :  img.title || ''}}
              <q-popup-edit v-model="img.label" buttons v-if="!readonly">
                <q-input v-model="img.title" dense autofocus counter />
              </q-popup-edit>
            </div>
            <div class="row justify-around" v-if="!readonly">
              <q-btn
                icon="keyboard_arrow_left"
                round
                color="secondary"
                size="sm"
                @click="moveImage(img.image, 'up')"
                v-if="idx > 0"
              >
                <q-tooltip>前移</q-tooltip>
              </q-btn>
              <q-btn label="删除" color="grey" dense size="sm" @click="removeImage(img.image)" />
              <q-btn
                icon="keyboard_arrow_right"
                round
                color="info"
                size="sm"
                @click="moveImage(img.image, 'down')"
                v-if="idx < scope.images.length - 1"
              >
                <q-tooltip>后移</q-tooltip>
              </q-btn>
            </div>
          </div>
          <div class="column full-height justify-center items-center" v-if="!readonly && !uploadLimit">
            <q-btn
              icon="add_to_photos"
              round
              size="xl"
              @click.prevent="clickFile"
            >
              <q-tooltip>增加图片</q-tooltip>
            </q-btn>
          </div>
        </div>

      </template>
    </viewer>
    <input ref="upload" multiple style="display: none;" type="file" accept="image/*" @change="fileAdded" />
  </div>
</template>

<script>
import { defineComponent } from 'vue'
import axios from 'axios';
import { uid } from 'quasar';
import EXIF from 'exif-js';

// 判断是否包含汉字
function existChineseCharacter (str) {
  if (/[\u4E00-\u9FA5\uFE30-\uFFA0]/.test(str)) {
    return true;
  } else {
    return false;
  }
}

export default defineComponent({
  props: {
    views: {
      type: Array,
      default: () => []
    },
    readonly: {
      type: Boolean,
      default: false
    },
    // 检验引用
    checkQuote: {
      type: Object,
      default: () => {
        return {
          type: '', // 后台对应的检查类型
          exclude_id: '' // 排除检查的id
        }
      }
    },
    limit: { // 图片可上传最大的数量 ， 0，不限制
      type: Number,
      default: 0
    }
  },
  emits: ['done', 'update:views'],
  data () {
    return {
      images: [],
      unlinkImages: [],
      label: '',
      step: 0,
      max: 0,
      compressSize: 100 * 1024, // 小于100K不压缩
      maxSize: 1024 * 1024, // 1M与1K之间默认不压缩，大于1M压缩
      promiseResult: null
    }
  },
  methods: {
    do () { // 上传图片，删除图片
      return new Promise((resolve, reject) => {
        // 只读不操作
        if (this.readonly) {
          this.$emit('done', this.images);
          resolve(this.images);
          return;
        }

        if (this.unlinkImages.length > 0) {
          axios.post('/c/system/unlinkImage', { images: this.unlinkImages });
        }

        if (this.images.filter(item => item.file).length > 0) {
          this.promiseResult = { resolve, reject };
          this.uploadImages();
        } else {
          this.$emit('done', this.images);
          resolve(this.images);
        }
      });
    },
    inited (viewer) {
      this.$viewer = viewer
    },
    show () {
      this.$viewer.show()
    },
    moveImage (image, direction) { // 移动图片 direction: up-前移 down-后移
      // 上移动一格
      function upGo (fieldData, index) {
        if (index != 0) {
          fieldData[index] = fieldData.splice(index - 1, 1, fieldData[index])[0];
        } else {
          fieldData.push(fieldData.shift());
        }
      }
      // 下移动一格
      function downGo (fieldData, index) {
        if (index != fieldData.length - 1) {
          fieldData[index] = fieldData.splice(index + 1, 1, fieldData[index])[0];
        } else {
          fieldData.unshift(fieldData.splice(index, 1)[0]);
        }
      }

      // 检索图片
      function indexOfImage (images, filename) {
        let idx = -1;
        for (let i = 0; i < images.length; i++) {
          if (images[i].image === filename) {
            idx = i;
            break;
          }
        }
        return idx;
      }
      const idx = indexOfImage(this.images, image);
      if (idx > -1) {
        switch (direction) {
          case 'up':
            upGo(this.images, idx);
            break;
          case 'down':
            downGo(this.images, idx);
            break;
        }
      }
      this.$emit('update:views', this.images); // 把变动发送出去
    },
    clickFile () { // 点击弹出选择图片
      this.$refs.upload.dispatchEvent(new MouseEvent('click'))
    },
    fileAdded (e) { // Input添加图片
      const batch = uid(); // 每次添加生成批次号
      this.label = '压缩图片';
      this.max = e.srcElement.files.length;
      this.step = 0;
      for (let idx = 0; idx < e.srcElement.files.length; idx++) {
        const file = e.srcElement.files[idx];
        this.imageHandle(file, batch, idx);
      }
      this.$refs.upload.value = null;
    },
    pushToImages (imgObj, batch, idx) { // 按顺序插入
      imgObj.batch = batch;
      imgObj.idx = idx;

      let finded = false;
      for (let i = 0; i < this.images.length; i++) {
        if (this.images[i].idx > idx && this.images[i].batch === batch) {
          finded = true;
          this.images.splice(i, 0, imgObj);
          break;
        }
      }

      !finded && this.images.push(imgObj);

      this.$emit('update:views', this.images); // 把变动发送出去
    },
    imageHandle (file, batch, idx) { // 参考 https://www.jb51.net/article/154415.htm
      const self = this;
      const compressSize = this.compressSize;
      const maxSize = this.maxSize;

      if (!/^image\//.test(file.type)) return; // 必须是图片才处理
      if (!window.FileReader) return; // 支持FileReader

      let title = '';
      // 是否包含中文
      if (existChineseCharacter(file.name)) {
        title = file.name.split('.').slice(0, -1).join('.');
      }
      // 创建filereader对象
      const reader = new FileReader();
      reader.readAsDataURL(file); // 将图片转成base64格式
      reader.onload = function () {
        const result = this.result;
        const img = new Image();
        img.src = result;
        // if (file.size <= compressSize) { // 无需压缩
        self.pushToImages({
          image: result,
          thumb: result,
          file,
          title,
          oriFile: { // 原始图
            image: result,
            thumb: result,
            file
          },
          canCompress: false, // 不可压缩
          compress: false // 是否压缩
        }, batch, idx);
        self.step += 1;
        /* } else {
          img.onload = async function () {
            // 获取旋转信息
            const orientation = await self._getOrientation(img);

            // 压缩图片
            const data = self._compress(img, orientation);
            const smFile = self._DataToFile(data, file.name, file.type)
            // 图片预览

            self.images.filter(v => v.image === data).length === 0 &&
              self.pushToImages({
                image: data,
                thumb: data,
                file: smFile,
                title,
                orientation,
                oriFile: { // 原始图
                  image: result,
                  thumb: result,
                  file
                },
                canCompress: true, // 可压缩
                compress: file.size > maxSize // 是否压缩
              }, batch, idx);
            self.step += 1;
          };
        } */
      };
    },
    _compress (img, orientation) { // 压缩图片
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');

      // 瓦片
      const tCanvas = document.createElement('canvas');
      const tctx = tCanvas.getContext('2d');
      const initSize = img.src.length;
      let width = img.width;
      let height = img.height;
      // 如果图片大于四百万像素，计算压缩比并将大小压至400万以下
      let ratio;
      if ((ratio = (width * height) / 4000000) > 1) {
        ratio = Math.sqrt(ratio);
        width /= ratio;
        height /= ratio;
      } else {
        ratio = 1;
      }
      canvas.width = width;
      canvas.height = height;
      // 修正拍照时旋转问题 参考 https://blog.csdn.net/duyan_s/article/details/105595101
      if (orientation == 6 || orientation == 8) {
        canvas.width = height;
        canvas.height = width;
        switch (orientation) {
          case 6: // 顺时转移拍照的图片 需要逆时针（向右）90度旋转 补偿
            ctx.rotate(-0.5 * Math.PI);
            ctx.translate(-width, 0);
            break
          case 8: // 需要顺时针（向左）90度旋转
            ctx.rotate(0.5 * Math.PI);
            ctx.translate(0, -height);
            break
        }
      }

      // 铺底色
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      // 如果图片像素大于100万则使用瓦片绘制
      let count;
      if ((count = (width * height) / 1000000) > 1) {
        count = ~~(Math.sqrt(count) + 1); // 计算要分成多少瓦片，~~在这里表示取整
        // 计算每块瓦片的宽高
        const nw = ~~(width / count);
        const nh = ~~(height / count);
        tCanvas.width = nw;
        tCanvas.height = nh;
        for (let i = 0; i < count; i++) {
          for (let j = 0; j < count; j++) {
            tctx.drawImage(
              img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh
            );
            ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
          }
        }
      } else {
        ctx.drawImage(img, 0, 0, width, height);
      }

      // 进行压缩
      const ndata = canvas.toDataURL('image/jpeg', 0.3);
      tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
      return ndata;
    },
    _DataToFile (data, name, type) { // datatul格式转file
      const text = window.atob(data.split(',')[1]);
      const buffer = new ArrayBuffer(text.length);
      const ubuffer = new Uint8Array(buffer);

      for (let i = 0; i < text.length; i++) {
        ubuffer[i] = text.charCodeAt(i);
      }

      const Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
      let blob;
      if (Builder) {
        const builder = new Builder();
        builder.append(buffer);
        blob = builder.getBlob(type);
      } else {
        blob = new window.Blob([ubuffer], { type });
      }

      // blob 转file
      const fileOfBlob = new File([blob], name, { type });
      return fileOfBlob;
    },
    uploadImages () { // 上传图片
      const self = this;
      this.label = '上传图片';

      const newImages = this.images.filter(item => item.file);
      if (newImages.length > 0) {
        const formdata = new FormData();
        newImages.forEach(image => {
          if (image.canCompress && image.compress) { // 原图上传
            formdata.append(image.file.name, image.file);
          } else { // 压缩图上传
            formdata.append(image.file.name, image.oriFile.file);
          }
        });
        const config = {
          headers: { 'Content-Type': 'multipart/form-data' },
          onUploadProgress: progressEvent => {
            if (progressEvent.lengthComputable) {
              self.step = progressEvent.loaded;
              self.max = progressEvent.total;
            }
          }
        };

        this.$axios.post('/upload', formdata, config).then(({ data }) => {
          for (const img of data.images) {
            const findedImg = this.images.filter(item => {
              return item.file && item.file.name === img.filename;
            })[0];
            findedImg.image = img.url;
            findedImg.thumb = img.thumb;

            findedImg.file && delete findedImg.file;
            findedImg.oriFile && delete findedImg.oriFile;
            findedImg.canCompress !== undefined && delete findedImg.canCompress;
            findedImg.compress !== undefined && delete findedImg.compress;
          }
          self.$emit('done', this.images);
          self.step = 0;
          self.max = 0;
          self.promiseResult.resolve(this.images);
        }).catch(err => {
          console.log(err);
          self.step = 0;
          self.max = 0;
        });
      }
    },
    removeImage (filename) { // 移除图片
      const self = this;
      this.images = this.images.filter(img => {
        if (img.image === filename) {
          if (!img.file) {
            // 检查是否被引用，若有引用则不删除
            const { type, exclude_id } = this.checkQuote;
            if (type && exclude_id) {
              axios.post('/c/system/checkQuoteFile', { type, exclude_id, file_path: img.image }).then(({ data }) => {
                if (!data.quoted) {
                  self.unlinkImages.push(img.image);
                  self.unlinkImages.push(img.thumb);
                }
              });
            } else {
              this.unlinkImages.push(img.image);// 添加到待删除图片任务
              this.unlinkImages.push(img.thumb);
            }
          }

          return false;
        } else {
          return true;
        }
      });
      this.$emit('update:views', this.images); // 把变动发送出去
    },
    imageUrl (img) {
      if (img.thumb.indexOf('blob') >= 0 || img.thumb.indexOf('data:image') >= 0) {
        return img.thumb;
      } else {
        return img.thumb + '?t=' + Date.now();
      }
    },
    // 获取图像的数据
    _getOrientation (img) {
      /* orientation值 旋转角度
        1 0°
        3 180°
        6 顺时针90°
        8 逆时针90° */
      return new Promise(resolve => {
        let orientation
        // 获取图像的旋转方向
        EXIF.getData(img, function () {
          EXIF.getAllTags(this)
          orientation = EXIF.getTag(this, 'Orientation')
          resolve(orientation)
        })
      })
    }
  },
  watch: {
    views: {
      handler (val) {
        this.images = Object.assign([], val && val.filter(v => !this.unlinkImages.includes(v.image)));
      },
      deep: true,
      immediate: true
    },
    step (val) {
      if (this.max > 0 && val >= this.max) {
        this.max = 0;
      }
    }
  },
  computed: {
    processLabel () {
      if (this.label == '压缩图片') {
        return `${this.label}... ${this.step} / ${this.max}`
      } else {
        return `${this.label}... ${~~(this.step / this.max * 100)}%`
      }
    },
    uploadLimit () { // 图片上传是否达到上传数量限制
      let limit = false;
      if (this.limit > 0) {
        limit = this.images.length >= this.limit;
      }
      return limit;
    }
  }
})
</script>
<style scoped>
.image-box {
  width: 140px;
  height: 140px;
  margin-bottom: 10px;
}

.view-container {
  width: 100%;
}
</style>
